import React, { useCallback, useEffect, forwardRef, useImperativeHandle, useRef } from 'react';
import { render } from 'react-dom';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { drawBuffer, VSHADER_SOURCE, FSHADER_SOURCE } from './bvf';
import { initShaders } from './shader';
import styles from './index.module.less';

const Index = forwardRef((props, ref) => {
  const { className } = props;

  useImperativeHandle(ref, () => ({
    drawArray: (p, array, w, h, l) => {
      if (array) {
        drawDance(p, array, w, h);
      }
    },
  }));

  const containeRef = useRef(null);
  const canRef = useRef(null);

  const onResize = useCallback(() => {
    const { offsetHeight: height, offsetWidth: width } = containeRef.current || {};

    const can = React.createElement('canvas', {
      width,
      height,
      style: { background: '#90202020' },
      ref: canRef,
    });
    containeRef.current && render(can, containeRef.current);

    const gl = canRef.current.getContext('webgl');
    if (gl) {
      initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);
      gl.viewport(0, 0, width, height);
      const widthHandle = gl.getUniformLocation(gl.program, 'width');
      const heightHandle = gl.getUniformLocation(gl.program, 'height');
      gl.uniform1f(widthHandle, width);
      gl.uniform1f(heightHandle, height);
    }
  }, []);

  useEffect(() => {
    const ro = new ResizeObserver(onResize);
    containeRef.current && ro.observe(containeRef.current);
    return () => {
      containeRef.current && ro.unobserve(containeRef.current);
      ro.disconnect();
    };
  }, []);

  const bvbv = (d) => {
    return new Uint8Array(d.buffer);
  };

  const drawDance = (palette, data, width, height, l) => {
    if (canRef.current) {
      const gl = canRef.current.getContext?.('webgl');
      if (gl) {
        const lengthHandle = gl.getUniformLocation(gl.program, 'length');
        gl.uniform1f(lengthHandle, l);

        const paletteLoc = gl.getUniformLocation(gl.program, 'u_Palette');
        gl.uniform1i(paletteLoc, 0);

        const samplerLoc = gl.getUniformLocation(gl.program, 'u_Sampler');
        gl.uniform1i(samplerLoc, 1);

        gl.activeTexture(gl.TEXTURE0);
        const texture1 = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture1);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 256 * 2, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, palette);

        gl.activeTexture(gl.TEXTURE1);
        const texture2 = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture2);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, bvbv(data));

        drawBuffer(gl, [-1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, -1.0], 'a_Position');

        gl.deleteTexture(texture1);
        gl.deleteTexture(texture2);
      }
    }
  };

  return <div ref={containeRef} className={classnames(styles.root, className)} />;
});

Index.defaultProps = {
  className: null,
};

Index.propTypes = {
  className: PropTypes.any,
};

export default Index;
